package view;

import java.awt.*;
import java.util.List;
import java.awt.event.*;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import javax.swing.*;
import model.Turtle;
import controller.TurtleController;
import util.commands.Command;
import util.coordTranslate.AbstractCoordinateSystem;

/**
 * A component that displays itself as an environment.
 * 
 * @author Robert C Duvall
 * @author Anne Weng and Nathan Klug
 */
public class Canvas extends JPanel
{
    private TurtleController myController;

    // used for serialization
    private static final long serialVersionUID = 1L;
    private JFrame myContainer;
    private Pixmap myPixmap;
    private List<Pixmap> myPixmaps;
    private Timer time;
    // private Camera myCamera;
    private Turtle animatedTurtle;
    private boolean animated;
    private double myPrevX, myPrevY, myNextX, myNextY, myInitX, myInitY;
    private double myFinalX, myFinalY, mySlopeX, mySlopeY;

    /**
     * Create canvas within the given Frame and with the given size.
     */
    public Canvas(TurtleController controller, JFrame container, Dimension size)
    {
        myController = controller;
        myContainer = container;
        animated = false;
        setSize(size);

        setBorder(BorderFactory.createLoweredBevelBorder());

        myPixmap = makePixmap();

        addComponentListener(new ComponentAdapter()
        {
            public void componentResized(ComponentEvent e)
            {
                myPixmap.containerSizeChanged(getSize());
            }
        });
        refresh();
    }

    private Pixmap makePixmap()
    {
        Pixmap pixmap = new Pixmap(getSize());
        Camera camera = new Camera(new Point(0, 0), getSize());
        pixmap.setCamera(camera);
        camera.setPixmap(pixmap);
        return pixmap;
    }

    /**
     * Apply the given command to this canvas.
     */
    public void execute(Command<Pixmap> command)
    {
        command.execute(myPixmap);
        refresh();
    }

    /**
     * Updates the environment
     */
    public void update(List<Pixmap> list)
    {
        myPixmaps = list;
        myPixmap = myPixmaps.get(0);
        refresh();
    }

    /**
     * Used for animation. TODO: See if we need this, or if it can be removed.
     * 
     * @param frame
     */
    public void displayFrame(int frame)
    {
        if (frame >= myPixmaps.size())
        {
            // TODO throw exception?
            return;
        }
        myPixmap = myPixmaps.get(frame);
        refresh();
    }

    /**
     * Animation method. TODO: Fix
     */
    public void doAnimation()
    {
        time = new Timer(100, new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                if (myInitX >= myFinalX || myInitY >= myFinalY)
                {
                    time.stop();
                }
                else
                {
                    paintLine(animatedTurtle, myInitX, myInitY, myNextX,
                            myNextY);
                    myInitX = myNextX;
                    myInitY = myNextY;
                    myNextX += mySlopeX;
                    myNextY += mySlopeY;
                    repaint();
                }
                /*
                 * if (animationCount == myEList.size() - 1) { animationCount =
                 * 0; time.stop(); } else { myPixmap =
                 * myEList.get(animationCount); animationCount++; refresh(); }
                 */
            }
        });
        time.start();
    }

    /**
     * Paints the image.
     */
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        myPixmap.tellCameraToDraw(g);
        BufferedImage original = myPixmap.getImageCopy();
        BufferedImage withTurtles = myPixmap.getImageCopy();
        drawTurtles((Graphics2D) withTurtles.getGraphics());
        myPixmap.setImage(withTurtles);
        myPixmap.tellCameraToDraw(g);
        myPixmap.setImage(original);
    }

    /**
     * Draws the line.
     * 
     * @param t
     * @param prevX
     * @param prevY
     * @param nextX
     * @param nextY
     */
    public void paintLine(Turtle t, double prevX, double prevY, double nextX, double nextY)
    {
        Point previousPoint = myPixmap.translateCoordinates(prevX, prevY);
        Point nextPoint = myPixmap.translateCoordinates(nextX, nextY);
        Line2D.Double lineToDraw = new Line2D.Double(previousPoint.x,
                previousPoint.y, nextPoint.x, nextPoint.y);
        t.getPen().drawShape(myPixmap, lineToDraw);
        // repaint();

    }

    /**
     * Changed so it tells the turtles to paint themselves. Now, mouseover
     * doesn't work, but I hope to implement it using a glassPane
     * 
     * Also adds the relevant turtle information as a Tool Tip which displays
     * whenever the mouse is held over the Canvas.
     */
    public void drawTurtles(Graphics2D g2)
    {
        myController
                .tellTurtlesToPaintThemselves(g2, myPixmap.getCoordinates());

        /*
         * Collection<Turtle> turtles = myController.getTurtles(); //Need <html>
         * tag in order to use linebreaks in a Tool Tip //String mouseOverText =
         * "<html>"; for (Turtle t : turtles){ //mouseOverText = mouseOverText +
         * turtleData(t) + "<br>"; t.draw(g2, myCoordinates); }
         * //this.setToolTipText(mouseOverText);
         */
    }

    /**
     * Gets all of the relevant data for a turtle and stores as a String.
     * 
     * @param t1
     * @return
     */
    public String turtleData(Turtle t)
    {
        StringBuilder sb = new StringBuilder();
        sb.append("Turtle " + t.getId() + " - ");
        sb.append("Heading: " + t.getAngle().getDegrees() + " degrees; ");
        sb.append("Location: (" + (int) t.getX() + " , " + (int) t.getY() + ")");
        return sb.toString();
    }

    public Pixmap getPixmap()
    {
        return myPixmap;
    }

    public void setPixmap(Pixmap pixmap)
    {
        myPixmap = pixmap;
    }

    /**
     * Used by the Controller to implement undo and redo
     * 
     * @return
     */
    public BufferedImage getCurrentBackground()
    {
        return myPixmap.getImageCopy();
    }

    /**
     * Change the size of this component so that it cannot be smaller than the
     * given size.
     * 
     * @param size
     */
    public void setSize(Dimension size)
    {
        setPreferredSize(size);
        setMinimumSize(size);
        super.setSize(size);
    }

    /**
     * Refresh the contents of the canvas by updating its size and pixmap.
     */
    private void refresh()
    {
        if (!myPixmap.getSize().equals(getSize()))
        {
            setSize(myPixmap.getSize());
            myContainer.pack();
        }
        repaint();
    }

    public void setScreen(BufferedImage image, AbstractCoordinateSystem coordinates)
    {
        myPixmap.setImage(image);
        myPixmap.setCoordinates(coordinates);
        paintComponent(this.getGraphics());
    }

    /**
     * The coordinate system being used by the canvas.
     * 
     * @return
     */
    public AbstractCoordinateSystem getCoordinates()
    {
        return myPixmap.getCoordinates();
    }

    private double findSlopeHelper(double numA, double numB)
    {
        if (numB <= 1.0e-6)
        {
            return numA;
        }
        else if (numA <= 1.0e-6)
        {
            return numB;
        }
        double remainder = numA % numB;
        return findSlopeHelper(numB, remainder);

    }

    public void setTurtle(Turtle turtle)
    {
        animatedTurtle = turtle;
    }

    public void findSlope()
    {
        double slopeX = myFinalX - myPrevX;
        double slopeY = myFinalY - myPrevY;
        double factor = findSlopeHelper(slopeY, slopeX);
        mySlopeX = slopeX / factor;
        mySlopeY = slopeY / factor;

    }

    public void setLocations(double prevX, double prevY, double nextX, double nextY)
    {
        myPrevX = prevX;
        myPrevY = prevY;
        myFinalX = nextX;
        myFinalY = nextY;
        findSlope();
        myInitX = myPrevX;
        myInitY = myPrevY;
        myNextX = myInitX + mySlopeX;
        myNextY = myInitY + mySlopeY;
    }

    /**
     * Clears the screen by creating a new image buffer.
     */
    public void clearScreen()
    {
        myPixmap.clearPixmap();

    }

    public void animate(boolean isAnimate)
    {
        animated = isAnimate;
    }

    public boolean isAnimated()
    {
        return animated;
    }

    public void zoomCamera(double zoomFactor)
    {
        myPixmap.zoom(zoomFactor);
    }

    public void moveCamera(int left, int up)
    {
        myPixmap.moveCamera(left, up);

    }
}
